package com.dap.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

import org.apache.commons.lang.reflect.FieldUtils;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dap.exception.ErrorCodeContainer;
import com.dap.exception.IllegalArgumentException;

/**
 * 参数校验
 */
public class ValidationAop {
    private final static Logger LOGGER = LoggerFactory.getLogger(ValidationAop.class);

    private ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();


    public void invoke(JoinPoint jp) throws IllegalArgumentException {
        Object[] arguments = jp.getArgs();
        validate(arguments);
    }


    public void validate(Object... arguments) throws IllegalArgumentException {

        Validator validator = validatorFactory.getValidator();
        ArrayList<Object> validateObjects = new ArrayList<>();

        Set<ConstraintViolation<?>> violations = new HashSet<>();
        for (Object arg : arguments) {
            validate(validator, violations, arg, validateObjects);
        }

        // 数据校验失败, 拼装错误信息
        if (violations.size() > 0) {
            StringBuilder message = new StringBuilder("\r\n");
            for (ConstraintViolation<?> cons : violations) {
                message.append(transolateMessage(cons)).append("\r\n");
            }
            throw new IllegalArgumentException(message.toString());
        }
    }


    private void validate(Validator validator, Set<ConstraintViolation<?>> violations, Object arg, List<Object> validateObjects) {
        for (Object validateObject : validateObjects) {
            // 只能使用地址相等, 调用方法可能出错
            if (validateObject == arg) {
                return;
            }
        }
        validateObjects.add(arg);


        if (arg != null && !isPrimitives(arg.getClass())) {
            if (Object[].class.isInstance(arg)) {
                for (Object item : (Object[]) arg) {
                    validate(validator, violations, item, validateObjects);
                }
            } else if (Collection.class.isInstance(arg)) {
                for (Object item : (Collection<?>) arg) {
                    validate(validator, violations, item, validateObjects);
                }
            } else if (Map.class.isInstance(arg)) {
                for (Map.Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) {
                    validate(validator, violations, entry.getKey(), validateObjects);
                    validate(validator, violations, entry.getValue(), validateObjects);
                }
            } else {
                for (Class<?> acls = arg.getClass(); acls != null && !isPrimitive(acls); acls =
                        acls.getSuperclass()) {

                    Field[] fields = acls.getDeclaredFields();

                    for (Field f : fields) {
                        // 校验属性为自定义对象的数据合法性
                        if (!isPrimitives(f.getDeclaringClass())) {
                            try {
                                Object nextObject = FieldUtils.readField(f, arg, true);
                                validate(validator, violations, nextObject, validateObjects);
                            } catch (IllegalAccessException e) {
                                LOGGER.warn("Validation {}", f.getName(), e);
                            }
                        }
                    }
                }
            }

            violations.addAll(validator.validate(arg));
        }
    }


    private static boolean isPrimitives(Class<?> cls) {
        if (cls.isArray()) {
            return isPrimitive(cls.getComponentType());
        }
        return isPrimitive(cls);
    }
    private static boolean isPrimitive(Class<?> cls) {
        return cls.isPrimitive() || cls == Class.class || cls == String.class || cls == Boolean.class
                || cls == Character.class || Number.class.isAssignableFrom(cls)
                || Date.class.isAssignableFrom(cls) || cls == Object.class;
    }


    /* 数据校验失败, 拼装错误信息 */
    private String transolateMessage(ConstraintViolation<?> constraintViolation) {
        Annotation annotation = constraintViolation.getConstraintDescriptor().getAnnotation();

        String annotationName = annotation.annotationType().getSimpleName();
        String property = constraintViolation.getPropertyPath().toString();
        // 如果定义了message，则使用message替代property
        String message = constraintViolation.getMessage().toString();
        if (null != message && message.trim().length() > 0) {
            property = message;
        }

        String invalidValue =
                constraintViolation.getInvalidValue() != null ? constraintViolation.getInvalidValue()
                    .toString() : "null";

        String[] params;
        switch (annotationName) {
        case "Max":
            Max max = (Max) annotation;
            params = new String[] { property, String.valueOf(max.value()), invalidValue };
            break;
        case "Min":
            Min min = (Min) annotation;
            params = new String[] { property, String.valueOf(min.value()), invalidValue };
            break;
        case "NotNull":
            params = new String[] { property };
            break;
        case "NotBlank":
            params = new String[] { property };
            break;
        case "Null":
            params = new String[] { property };
            break;
        case "Pattern":
            params = new String[] { property, invalidValue };
            break;
        case "Size":
            Size size = (Size) annotation;
            params = new String[] { property, String.valueOf(size.min()), String.valueOf(size.max()), invalidValue };
            break;
        case "DapDateFormat":
			params = new String[] { property, invalidValue};
			break;
        default:
            LOGGER.warn("Unsupport transolate annotation [{}]", annotationName);
            return "";
        }

        return ErrorCodeContainer.getErrorMessage(annotationName, params);
    }
}
